package com.youyun56.gps.schedual;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.sql.SQLException;
import java.util.Date;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.j3.sql.GridUtils;
import com.j3.sql.SqlFunc;
import com.youyun56.commons.DateUtils;
import com.youyun56.pojo.Car;

public class GPS implements Runnable {

    public static ThreadPoolExecutor writers;
    public static ThreadPoolExecutor readers;
    public final static ServiceStatusType status = ServiceStatusType.active;
    public final static ConcurrentMap<SocketChannel, Car> currentCars = new ConcurrentHashMap<SocketChannel, Car>();
    public final static ConcurrentHashMap<Future<?>, GPSReader> tracerMapper = new ConcurrentHashMap<Future<?>, GPSReader>();
    public static InetSocketAddress target = new InetSocketAddress(GPSFactory.getIP(), GPSFactory.getPort());
    public static CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
    private Long maxId = -1L;
    public final static LinkedBlockingQueue<Car> cars = new LinkedBlockingQueue<Car>();
    private ScheduledFuture<?> scheduledFuture;
    public static ScheduledExecutorService readerCheckService = Executors.newSingleThreadScheduledExecutor();
    private final static Log log = LogFactory.getLog(GPS.class);
    private Long start;

    public GPS() throws UnknownHostException, IOException, GPSLoginException, SQLException {
        catchCars();
        GPS.writers = new ThreadPoolExecutor(GPSFactory.getThreadNum(), GPSFactory.getThreadNum(), 0L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());
        GPS.readers = new ThreadPoolExecutor(GPSFactory.getThreadNum(), GPSFactory.getThreadNum(), 0L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>());
        for (int i = 0; i < GPSFactory.getThreadNum(); i++) {
            SocketChannel client = SocketChannel.open();
            // 设置为非阻塞模式
            client.configureBlocking(false);
            // 打开选择器
            Selector selector = Selector.open();
            // 注册连接服务端socket动作
            client.register(selector, SelectionKey.OP_CONNECT);
            // 连接
            client.connect(target);
            writers.submit(new GPSWriter(cars, client, new Semaphore(1), selector));
        }
        this.scheduledFuture = readerCheckService.scheduleAtFixedRate(new TimeoutCheckTask(), 30, GPSFactory.getReaderCheckSpan(), TimeUnit.SECONDS);
        start = System.currentTimeMillis();
    }

    @Override
    public void run() {

        while (status == ServiceStatusType.active) {
            try {
                if (cars.size() < GPSFactory.getTimeLoadNum()) {
                    log.info("Fetching  Cars...");
                    if (this.maxId > -2) {
                        catchCars();
                    } else if (this.maxId == -2L) {
                        if (cars.size() == 0) {
                            long now = System.currentTimeMillis();
                            log.info(String.format("One Cycle Finish! Cost %ds", (now - start) / 1000));
                            start = now;
                            catchCars();
                        }
                    }
                }
                Thread.sleep(GPSFactory.getLoadTimeSpan() * 1000);
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                log.error(e.getMessage(),e);
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.error(e.getMessage(),e);
            }
        }

    }

    private void catchCars() throws SQLException {
        GridUtils util = new GridUtils();
        util.init(String.format("select id,carMobile from car where id > %d and status = 'VIP' and gps = true order by id asc limit %d", maxId,
                GPSFactory.getTimeLoadNum()));
        log.info(String.format("Fetch %d Cars", util.getFetchRows()));
        if (util.getFetchRows() > 0) {
            while (util.next()) {
                Car car = new Car();
                if (this.maxId < util.getLong("id")) {
                    this.maxId = util.getLong("id");
                }
                car.setId(util.getLong("id"));
                car.setCarMobile(util.get("carMobile"));
                cars.add(car);
            }
        } else {
            this.maxId = -2L;
            // Thread.sleep(GPSFactory.getLoadTimeSpan());
        }
    }

    private static class TimeoutCheckTask implements Runnable {

        private Logger logger = LoggerFactory.getLogger(TimeoutCheckTask.class);

        @Override
        public void run() {
            long current = System.currentTimeMillis();
            for (Entry<Future<?>, GPSReader> entry : GPS.tracerMapper.entrySet()) {
                Future<?> future = entry.getKey();
                GPSReader reader = entry.getValue();
                if (!future.isDone()) {
                    if (reader.getStart() != null) {
                        if (GPSFactory.getTimeout() * 1000 < (current - reader.getStart())) {
                            GPS.tracerMapper.remove(future);
                            reader.release();
                            future.cancel(true);
                            String mobile = GPS.currentCars.get(reader.getClient()).getCarMobile();
                            log.warn(String.format("Reader Mobile %s cost %d longer than Config Time %d", mobile,
                                    (current - reader.getStart()) / 1000, GPSFactory.getTimeout()));
                            SqlFunc func = new SqlFunc();
                            try {
                                func.executeUpdate(GPSFactory.localDB, String.format(
                                        "insert into claimers(mobile,startTime,endTime,status) values('%s','%s','%s',%d)", mobile,
                                        DateUtils.FULL_DATE_FORMAT.format(new Date(reader.getStart())),
                                        DateUtils.FULL_DATE_FORMAT.format(new Date()), ClaimerStatus.TIMEOUT.ordinal()));
                            } catch (Exception e) {
                                // TODO Auto-generated catch block
                                // e.printStackTrace();
                                log.error(e.getMessage(),e);
                            }
                        }
                    } else {
                        log.error(String.format("%s Reader Start Failure", GPS.currentCars.get(reader.getClient()).getCarMobile()));
                    }
                } else {
                    GPS.tracerMapper.remove(future);
                }
            }
        }
    }

    public static void shutdown() {
        try {
            for (Entry<SocketChannel, Car> entry : GPS.currentCars.entrySet()) {
                entry.getKey().close();
            }
            if (GPS.writers != null) {
                GPS.writers.shutdown();
            }
            if (GPS.readers != null) {
                GPS.readers.shutdown();
            }
            if (GPS.readerCheckService != null) {
                GPS.readerCheckService.shutdown();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            log.error(e.getMessage(),e);
        }
    }

}
